package com.wb.spring.boot.autoconfigure.proxy;

import com.wb.spring.boot.autoconfigure.annotation.WbClient;
import com.wb.spring.boot.autoconfigure.register.IRegister;
import com.wb.spring.boot.autoconfigure.stereotype.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ExecutionException;

/**
 * @author sw
 * @create 2023-05-17 12:16
 */
public class CglibProxyFactory<T> implements MethodInterceptor {
    static Logger logger = LoggerFactory.getLogger(CglibProxyFactory.class);

    private Class<?> target;

    // 全局异步请求开关
    private final boolean globalAsync;
    // 全局默认最多重试次数
    private final int globalMaxAttempts;

    private final String serviceName;

    private final String path;

    private final Boolean async;

    private final Integer maxAttempts;

    private final BeanFactory beanFactory;

    private final Class<?> fallback;

    private final Class<?> fallbackFactory;

    public CglibProxyFactory(Class<?> target, String serviceName, String path, boolean async, int maxAttempts, BeanFactory beanFactory, Class<?> fallback, Class<?> fallbackFactory) {
        this.target = target;
        this.serviceName = serviceName;
        this.path = path;
        this.async = async;
        this.maxAttempts = maxAttempts;
        this.beanFactory = beanFactory;
        this.globalAsync = false;
        this.globalMaxAttempts = 0;
        this.fallback = fallback;
        this.fallbackFactory = fallbackFactory;
    }

    public CglibProxyFactory(Class<?> target, String serviceName, String path, boolean async, int maxAttempts, BeanFactory beanFactory, Class<?> fallback, Class<?> fallbackFactory, boolean globalAsync, int globalMaxAttempts) {
        this.target = target;
        this.serviceName = serviceName;
        this.path = path;
        this.async = async;
        this.maxAttempts = maxAttempts;
        this.beanFactory = beanFactory;
        this.globalAsync = globalAsync;
        this.fallback = fallback;
        this.fallbackFactory = fallbackFactory;
        this.globalMaxAttempts = globalMaxAttempts;
    }

    // 创建代理对象
    public Object getProxyInstance() {
        logger.info(" getProxyInstance = {}", this.target);
        // 1.cglib工具类
        Enhancer en = new Enhancer();
        // 2.设置父类
        en.setSuperclass(this.target);
        // 3.设置回调函数
        en.setCallback(this);

        return en.create();
    }

    //拦截方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        Arrays.stream(args).forEach(System.out::println);

        try {
            WbRequestMapping annotation = AnnotationUtils.findAnnotation(method, WbRequestMapping.class);
            if (annotation == null) {
                return get(method, args);
            }
            switch (annotation.method()) {
                case DELETE:
                    return delete(method, args);
                case PUT:
                    return put(method, args);
                case POST:
                    return post(method, args);
                case GET:
                default:
                    return get(method, args);
            }
        } catch (Exception e) {
            // 处理回调业务
            return handleFallBack(e, method, args);
        }
    }


    private <T> T get(Method method, Object[] args) throws Throwable {
        // 1.找到注解
//        WbGetMapping requestMapping = AnnotationUtils.findAnnotation(method, WbGetMapping.class);
        WbRequestMapping requestMapping = AnnotationUtils.findAnnotation(method, WbRequestMapping.class);

        Assert.notNull(requestMapping, String.format("方法%s不能为空", method.getName()));
        // 3.拼接url。注意该url并不是一个可以直接调用的url，需要经过自定义的拦截器的处理。
        StringBuilder urlBuilder = initUrlBuilder(requestMapping.value());
        // 4.处理参数
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        HttpEntity<Object> httpEntity = null;
        for (int i = 0; i < args.length; i++) {
            // 获取方法上的参数
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation requestArgAnnotation = parameterAnnotations[i][j];
                Parameter parameter = parameters[i];
                Object arg = args[i];
                // 如果注解是@WbRequestParam 则拼接到url后面
                if (arg == null) {
                    continue;
                }
                if (requestArgAnnotation instanceof WbRequestParam) {
                    if (urlBuilder.indexOf("?") < 0) {
                        urlBuilder.append("?");
                    } else {
                        urlBuilder.append("&");
                    }
                    String paramName = ((WbRequestParam) requestArgAnnotation).value();
                    paramName = (StringUtils.isNotBlank(paramName) ? paramName : parameter.getName());
                    urlBuilder.append(paramName)
                            .append("=")
                            .append(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody && (httpEntity == null)) {
                    httpEntity = new HttpEntity<>(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody) {
                    throw new IllegalArgumentException("one method can't have two @WbRequestBody");
                }
            }
        }
        return (T) execute(urlBuilder.toString(), HttpMethod.GET, httpEntity, method.getReturnType());
    }

    private <T> T post(Method method, Object[] args) throws Throwable {
        // 1.找到注解
        WbRequestMapping requestMapping = AnnotationUtils.findAnnotation(method, WbRequestMapping.class);
//        WbPostMapping requestMapping = AnnotationUtils.findAnnotation(method, WbPostMapping.class);
        Assert.notNull(requestMapping, String.format("方法%s不能为空", method.getName()));
        // 2.拼接url，以&开头，表示是SimpleFeign的rpc调用。注意该url并不是一个可以直接调用的url，需要经过自定义的拦截器的处理。
        StringBuilder urlBuilder = initUrlBuilder(requestMapping.value());
        // 3.处理参数
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        HttpEntity<Object> httpEntity = null;
        for (int i = 0; i < args.length; i++) {
            // 获取方法上的参数
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation requestArgAnnotation = parameterAnnotations[i][j];
                Parameter parameter = parameters[i];
                Object arg = args[i];
                // 如果注解是@WbRequestParam 则拼接到url后面
                if (arg == null) {
                    continue;
                }
                if (requestArgAnnotation instanceof WbRequestBody && (httpEntity == null)) {
                    httpEntity = new HttpEntity<>(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody) {
                    throw new IllegalArgumentException("one method can't have two @WbRequestBody");
                }
            }
        }
        return (T) execute(urlBuilder.toString(), HttpMethod.POST, httpEntity, method.getReturnType());
    }

    private <T> T put(Method method, Object[] args) throws Throwable {
        // 1.找到注解
        WbRequestMapping requestMapping = AnnotationUtils.findAnnotation(method, WbRequestMapping.class);
//        WbPutMapping requestMapping = AnnotationUtils.findAnnotation(method, WbPutMapping.class);
        Assert.notNull(requestMapping, String.format("方法%s不能为空", method.getName()));
        // 2.拼接url，以&开头，表示是SimpleFeign的rpc调用。注意该url并不是一个可以直接调用的url，需要经过自定义的拦截器的处理。
        StringBuilder urlBuilder = initUrlBuilder(requestMapping.value());
        // 3.处理参数
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        HttpEntity<Object> httpEntity = null;
        for (int i = 0; i < args.length; i++) {
            // 获取方法上的参数
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation requestArgAnnotation = parameterAnnotations[i][j];
                Parameter parameter = parameters[i];
                Object arg = args[i];
                // 如果注解是@WbRequestParam 则拼接到url后面
                if (arg == null) {
                    continue;
                }
                if (requestArgAnnotation instanceof WbRequestBody && (httpEntity == null)) {
                    httpEntity = new HttpEntity<>(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody) {
                    throw new IllegalArgumentException("one method can't have two @WbRequestBody");
                }
            }
        }
        return (T) execute(urlBuilder.toString(), HttpMethod.PUT, httpEntity, method.getReturnType());
    }


    private <T> T delete(Method method, Object[] args) throws Throwable {
        // 1.找到注解
//        WbGetMapping requestMapping = AnnotationUtils.findAnnotation(method, WbGetMapping.class);
        WbRequestMapping requestMapping = AnnotationUtils.findAnnotation(method, WbRequestMapping.class);

        Assert.notNull(requestMapping, String.format("方法%s不能为空", method.getName()));
        // 3.拼接url。注意该url并不是一个可以直接调用的url，需要经过自定义的拦截器的处理。
        StringBuilder urlBuilder = initUrlBuilder(requestMapping.value());
        // 4.处理参数
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        HttpEntity<Object> httpEntity = null;
        for (int i = 0; i < args.length; i++) {
            // 获取方法上的参数
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation requestArgAnnotation = parameterAnnotations[i][j];
                Parameter parameter = parameters[i];
                Object arg = args[i];
                // 如果注解是@WbRequestParam 则拼接到url后面
                if (arg == null) {
                    continue;
                }
                if (requestArgAnnotation instanceof WbRequestParam) {
                    if (urlBuilder.indexOf("?") < 0) {
                        urlBuilder.append("?");
                    } else {
                        urlBuilder.append("&");
                    }
                    String paramName = ((WbRequestParam) requestArgAnnotation).value();
                    paramName = (StringUtils.isNotBlank(paramName) ? paramName : parameter.getName());
                    urlBuilder.append(paramName)
                            .append("=")
                            .append(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody && (httpEntity == null)) {
                    httpEntity = new HttpEntity<>(arg);
                } else if (requestArgAnnotation instanceof WbRequestBody) {
                    throw new IllegalArgumentException("one method can't have two @WbRequestBody");
                }
            }
        }
        return (T) execute(urlBuilder.toString(), HttpMethod.DELETE, httpEntity, method.getReturnType());
    }

    private StringBuilder initUrlBuilder(String uri) {
        Assert.notNull(uri, "uri is Empty!");
        IRegister register = beanFactory.getBean(IRegister.class);
        String instanceAddress = register.getServer(serviceName);

        StringBuilder urlBuilder = new StringBuilder("http://");
        StringBuilder pathBuilder = new StringBuilder("")
                .append(instanceAddress)
                .append("/");
        if (StringUtils.isNotBlank(path)) {
            pathBuilder.append(path).append("/");
        }
        pathBuilder.append(uri);
        // 替换重复
        String path = pathBuilder.toString();
        String s = path.replaceAll("/+", "/");
        return urlBuilder.append(s);
    }

    private String randomServer(List<String> instanceList) {
        int index = new Random().nextInt(instanceList.size());
        return instanceList.get(index);
    }

    private <T> T execute(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType) throws Throwable {
        logger.info("execute params { url = {}, httpMethod = {}, httpEntity = {},  returnType = {}, async = {}, maxAttempts = {} }", url, httpMethod, httpEntity, returnType, async, maxAttempts);
        Integer tmpMaxAttempts = (maxAttempts > 0) ? maxAttempts : (Math.max(globalMaxAttempts, 0));
        if (tmpMaxAttempts > 0) {
            return maxAttemptCall(url, httpMethod, httpEntity, returnType, async, tmpMaxAttempts);
        } else {
            return basicCall(url, httpMethod, httpEntity, returnType, async);
        }

    }


    public <T> T maxAttemptCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType, boolean async, int maxAttempts) throws Throwable {
        if (globalAsync || async) {
            return maxAttemptAsyncCall(url, httpMethod, httpEntity, returnType, maxAttempts);
        }
        return maxAttemptSyncCall(url, httpMethod, httpEntity, returnType, maxAttempts);
    }

    public <T> T maxAttemptAsyncCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType, int maxAttempts) throws Throwable {
        int step = 0;
        Throwable throwable = null;

        // TODO
        for (step = 0; step < maxAttempts; step++) {
            try {
                return asyncCall(url, httpMethod, httpEntity, returnType);
            } catch (Exception e) {
                throwable = e;
            }
        }
        if (step >= maxAttempts) {
            throw throwable;
        }
        return null;
    }

    public <T> T maxAttemptSyncCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType, int maxAttempts) throws Throwable {
        int step = 0;
        Throwable throwable = null;
        //
        for (step = 0; step < maxAttempts; step++) {
            try {
                return syncCall(url, httpMethod, httpEntity, returnType);
            } catch (Exception e) {
                throwable = e;
            }
        }
        if (step >= maxAttempts) {
            throw throwable;
        }
        return null;
    }


    public <T> T basicCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType, boolean async) throws ExecutionException, InterruptedException {
        if (globalAsync || async) {
            return asyncCall(url, httpMethod, httpEntity, returnType);
        }
        return syncCall(url, httpMethod, httpEntity, returnType);
    }


    /**
     * 同步调用
     *
     * @param url
     * @param httpMethod
     * @param httpEntity
     * @param returnType
     * @param <T>
     * @return
     */
    public <T> T syncCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType) throws ExecutionException, InterruptedException {


        ResponseEntity<T> entityBody = null;
        AsyncRestTemplate simpleFeignTemplate = (AsyncRestTemplate) beanFactory.getBean("asyncRestTemplate");
        ListenableFuture<ResponseEntity<T>> exchange = simpleFeignTemplate.exchange(url, httpMethod, httpEntity, returnType);
        entityBody = exchange.get();
        if (returnType == Void.class) {
            return null;
        }
        return Optional.ofNullable(entityBody).isPresent() ? entityBody.getBody() : null;

    }

    /**
     * 异步调用
     *
     * @param url
     * @param httpMethod
     * @param httpEntity
     * @param returnType
     * @param <T>
     * @return
     */
    public <T> T asyncCall(String url, HttpMethod httpMethod, HttpEntity<Object> httpEntity, Class<T> returnType) {

        ResponseEntity<T> entityBody = null;
        RestTemplate simpleFeignTemplate = (RestTemplate) beanFactory.getBean("restTemplate");
        entityBody = simpleFeignTemplate.exchange(url, httpMethod, httpEntity, returnType);

        if (returnType == Void.class) {
            return null;
        }
        return Optional.ofNullable(entityBody).isPresent() ? entityBody.getBody() : null;
    }


    public <T> T handleFallBack(Throwable e, Method method, Object[] args) throws Throwable  {
        try {
            if (fallback != void.class) {
                Class.forName(fallback.getName());
                Constructor con = fallback.getConstructor();
                Object o = con.newInstance();

//                String methodName = method.getName();
//                Method method2 = fallback.getMethod(methodName, method.getParameterTypes());
//                return (T) method2.invoke(o, args);
                return (T) method.invoke(o, args);

            }
            if (fallbackFactory != void.class) {
//                String methodName = method.getName();
                Constructor con = fallbackFactory.getConstructor();
                Method method1 = fallbackFactory.getMethod("create");
                Object o = con.newInstance();
                Object o2 = method1.invoke(o);
                return (T) method.invoke(o2, args);
            }
            throw e;
        } catch (Exception e2) {
            logger.error("handleFallBack exception  = ", e2);
            throw e;
        }
    }

}
